﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.IO;
using System.Xml;
using Grishko.SerilizeTools;
using System.Reflection;
using Grishko.DataTier.MetaInfo;
using System.Globalization;

namespace Grishko.DataTier
{
    public delegate void AddChild(object obj);
    public class EntityList : Entity, IEnumerator, IEnumerable
    {
        protected class HasComparer : IEqualityComparer<EntitySingle>
        {
            public bool Equals(EntitySingle x, EntitySingle y)
            {
                if (x.ID.Equals(y.ID))
                    return true;
                else
                    return false;
            }

            public int GetHashCode(EntitySingle obj)
            {

                Func<string, Int32> f = delegate(string s)
                {
                    Int32 res = 0;
                    foreach (char i in s.ToCharArray())
                    {
                        res = res + (int)i;

                    }
                    return res;
                };
                EntitySingle v = (EntitySingle)obj;
                return f(v.ID);
            }

        }

        public EntityList()
            : base()
        {   

        }

        private List<EntitySingle> _List = new List<EntitySingle>();
        private int _Index = -1;

        #region Tree
        public event AddChild OnAddChild;

        #endregion

        #region IEnumerable Members

        public IEnumerator GetEnumerator()
        {
            return this;
        }

        #endregion

        #region IEnumerator Members

        [EntityNonSerialized]
        public object Current
        {
            get
            {
                try
                {
                    return _List[_Index];

                }
                catch
                {
                    throw;
                }
            }
        }

        public bool MoveNext()
        {
            try
            {
                if (0 == _List.Count) return false;
                if (_Index > _List.Count || _Index == (_List.Count)) Reset();
                _Index++;
                bool r = (_List.Count != _Index);
                return r;
            }
            catch
            {
                return false;
            }
        }

        public void Reset()
        {
            _Index = -1;
        }

        public virtual void Add(EntitySingle obj)
        {
            string id=obj.ID .ToUpper ();
            for (int i = 0; i < _List.Count; i++)
            {
                if (id.Equals(_List[i].ID.ToUpper()))
                {
                    _List.Remove(_List[i]);
                    _List.Add(obj);
                    return;
 
                 //  throw new Exception("ID коллекции не уникален");
                 
                }
            }

            _List.Add(obj);
        }

        public virtual void Add(ICollection obj)
        {
            foreach (EntitySingle i in obj)
            {
                Add(i);
            }

        }

        public void Remove(EntitySingle obj)
        {
            _List.Remove(obj);
        }

        public void Remove(string ID)
        {
            EntitySingle f = _List.Find(delegate(EntitySingle Entity)
            {
                return Entity.ID.Equals(
                    ID, StringComparison.OrdinalIgnoreCase);
            });
            if (null != f) _List.Remove(f);
        }

        [EntityNonSerialized]
        public virtual EntitySingle this[object KeyValue]
        {
            get
            {
                int r = _Index;
                try
                {
                    Reset();
                    string sKeyValue = KeyValue.ToString ();
                    for (int i = 0; i < this._List.Count; i++)
                    {
                        if ( sKeyValue== _List[i].ID )
                                            return _List[i];
                    }

                    return null;
                }

                catch
                {
                    throw;
                }
                finally
                {
                    _Index = r;
                }

            }
        }

        [EntityNonSerialized]
        public virtual EntitySingle this[int i]
        {
            get
            {

                return _List[i];

            }
        }

        [EntityNonSerialized]
        public int Count { get { return _List.Count; } }

        public void Clear()
        {
            _List.Clear();
        }

        public EntitySingle GetFirst()
        {
            Reset();
            foreach (EntitySingle i in this) return i;
            return null;
        }

        #endregion

        #region Override

        internal  override void WriteXml(System.Xml.XmlWriter writer, string ObjectName, bool Full)
        {
            if (0 == Count) return;

            // для Sql списков - тока короткие деревья
            if (ImSql)
            {
                WriteShortXml(writer, ObjectName);
                return;
            }

            Type MyType = this.GetType();

            writer.WriteStartElement(ObjectName);
            //// Обязательно пишем тип и сборку 
            writer.WriteAttributeString(SerilizeContext.TypePref, MyType.ToString());
            writer.WriteAttributeString(SerilizeContext.AssemblyPref, MyType.Assembly.ToString());


            // пишем примитивные типы 
            foreach (PropertyInfo inf in AttPropertiesList)
            {
                object v = inf.GetValue(this, null);
                if (null == v) continue;
                if (String.IsNullOrEmpty(v.ToString())) continue;
                writer.WriteAttributeString(inf.Name, v.ToString());
            }
            
            //Пишем список объектов
            this.Reset();
            foreach (EntitySingle item in this)
            {
                // если объект удален нефиг его серилизовать
                if (item.State == EntityState.Deleted) continue;

                if (item.ImSql && !Full) // если объект поддерживает ImSql и это режим короткой записи - пишем короткий xml 
                {
                    ((Entity)item).WriteShortXml(writer, ((IEntity)item).TagName);
                    continue;
                }
                ((Entity)item).WriteXml(writer, ((IEntity)item).TagName, Full);
            }

            // конец узла
            writer.WriteEndElement();
        }

        /// <summary>
        /// Краткая запись объектов списка в формате         
        /// </summary>
        /// <param name="writer"></param>
        /// <param name="ObjectName"></param>
        internal  override void WriteShortXml(XmlWriter writer, string ObjectName)
        {
            if (0 == Count) return;

            Type MyType = this.GetType();

            writer.WriteStartElement(ObjectName);
            //// Обязательно пишем тип и сборку 
            writer.WriteAttributeString(SerilizeContext.TypePref, MyType.ToString());
            writer.WriteAttributeString(SerilizeContext.AssemblyPref, MyType.Assembly.ToString());

            Type t = MyGetType(MyType.Assembly.ToString(), MyType.ToString());

            foreach (PropertyInfo inf in AttPropertiesList)
            {
                object v = inf.GetValue(this, null);
                if (null == v) continue;
                if (String.IsNullOrEmpty(v.ToString())) continue;
                writer.WriteAttributeString(inf.Name, v.ToString());
            }

            this.Reset();
            foreach (EntitySingle item in this)
            {
                if (item.ImSql)
                    ((Entity)item).WriteShortXml(writer, "Item");
                else
                    ((Entity)item).WriteXml(writer, "Item", true);
            }

            // конец узла
            writer.WriteEndElement();

        }

        public  override void ReadXml(System.Xml.XmlNode Node)
        {
            base.ReadXml(Node);
            foreach (System.Xml.XmlNode nd in Node.ChildNodes)
            {
                string sAssmblyName = (ItemAssemblyName.Length == 0 ? nd.Attributes[SerilizeContext.AssemblyPref].Value : ItemAssemblyName);
                string sItemAssemblyType = (ItemAssemblyType.Length == 0 ? nd.Attributes[SerilizeContext.TypePref].Value : ItemAssemblyType);

                //// это кэшируемый объект?
                //XmlAttribute IsCacheObject = nd.Attributes["IsCacheObject"];
                //if (null != IsCacheObject)
                //{
                //    string ID = nd.Attributes["ID"].Value;
                //    if (CacheTools.IsCacheObject(ID))
                //    {
                //        this.Add((EntitySingle) CacheTools.GetCacheObect(ID));
                //        continue;
                //    }


                //}
                
                Type Tr = ReflectionItem.MyGetType(sAssmblyName, sItemAssemblyType);
                if(null==Tr)
                        throw new Exception ( string .Format ("Тип {0}{1} не найден",new string[] {sAssmblyName, sItemAssemblyType}));

                Entity obj = (Entity)Activator.CreateInstance(Tr);
                if (!(obj is EntityList))
                {
                    obj.ReadXml(nd);
                    Add(obj as EntitySingle);
                }
            }

        }

        public override void SaveWitchChild()
        {
            try
            {
                if (0 == Count) return;
                Reset();
                foreach (EntitySingle item in this) ((EntitySingle)item).SaveWitchChild ();
            }
            catch (Exception e)
            {
                throw;
            }
        }
        public override void Save()
        {

            try
            {
                if (0 == Count) return;
                Reset();
                foreach (EntitySingle item in this) ((EntitySingle)item).Save();
            }
            catch(Exception e)
            {
                throw;
            }

        }

        public override void Load()
        {
            ///Если список поддерживает ISqlSignature
            if (ImSupprotType(this.GetType().GetInterfaces(), typeof(ISqlSignature)))
            {
                SecuretyContext SC = AppContext.GetSC();
                try
                {
                    ISqlSignature o = (ISqlSignature)this;
                    SqlTools tls = new SqlTools(SC);
                    XmlReader r = tls.ReadXml(o.LoadProcedure, this);
                    if (null == r) return;
                    XmlDocument d = new XmlDocument();
                    d.Load(r);
                    if (0 == d.ChildNodes.Count) return;
                    if (null == d.FirstChild) return;
                    this.Clear();
                    this.ReadXml(d.FirstChild);

                    foreach (Entity item in this)
                        item.SetHash();
                }
                catch
                {
                    throw;
                }
                finally
                {
                    SC.Connection.Close();
                }
                return;
            }

            //Иначе
            try
            {
                this.Reset();
                foreach (EntitySingle item in this)
                {
                    ((EntitySingle)item).Load();
                }
            }
            catch
            {
                throw;
            }
        }

        /// <summary>
        /// Формирование поисковой информации и информации о объектах списка         
        /// </summary>
        /// <param name="res"></param>
        /// <param name="obj"></param>
        /// <param name="RootId"></param>
        internal  override   void PopulateSrchItemes(SrchInfo res, Entity obj, string RootId,string XPatch )
        {  
            //Объектные свойства 
            foreach (Entity item in this)
            {                
                if (null == item) 
                                continue;
                if (item is ISqlSignature)
                                continue;                  
                string[] p= {item.TagName,((EntitySingle)item).ID } ;                                 
                        item.PopulateSrchItemes(res, (item as Entity), RootId, XPatch+(item as EntitySingle).GetXPatch(p));
            }
        }
        #endregion

        #region Virtual
        [EntityNonSerialized]
        public virtual string ListXML
        {
            get
            {
                if (0 == Count) return string.Empty;
                StringBuilder sb;
                MemoryStream ms = new MemoryStream();
                sb = new StringBuilder();
                StringWriter sw = new StringWriter(sb, System.Globalization.CultureInfo.InvariantCulture);
                XmlTextWriter w = new XmlTextWriter(sw);
                w.Formatting = Formatting.Indented;
                w.Indentation = 3;

                Reset();
                foreach (EntitySingle item in this)
                {
                    w.WriteStartElement("Item");
                    w.WriteAttributeString("ID", item.ID);
                    w.WriteEndElement();
                }

                return sb.ToString();

            }
        }

        #region ToList
        public List<EntitySingle> ToList()
        {
            return ToList<EntitySingle>();
        }

        protected class EntitySingleHasComparer : IEqualityComparer<EntitySingle>
        {
            public bool Equals(EntitySingle x, EntitySingle y)
            {
                return x.ID.Equals(y.ID);
            }

            public int GetHashCode(EntitySingle obj)
            {

                Func<string, Int32> f = delegate(string s)
                {
                    Int32 res = 0;
                    foreach (char i in s.ToCharArray())
                    {
                        res = res + (int)i;

                    }
                    return res;
                };
                EntitySingle v = (EntitySingle)obj;
                return f(v.ID);
            }
        }

        public virtual HashSet<EntitySingle> ToHashSet()
        {
            EntitySingleHasComparer cmp = new EntitySingleHasComparer();
            HashSet<EntitySingle> res = new HashSet<EntitySingle>(cmp);
            this.Reset();
            foreach (EntitySingle item in this)
            {
                res.Add(item);
            }
            return res;



        }

        public virtual HashSet<T> ToHashSet<T>(IEqualityComparer<T> cmp)
        {
            HashSet<T> res = new HashSet<T>(cmp);
            this.Reset();
            foreach (T item in this)
            {
                res.Add(item);
            }
            return res;



        }

        public List<T> ToList<T>()
        {
            List<T> r = new List<T>();
            Reset();
            foreach (T item in this)
            {
                r.Add(item);
            }
            return r;
        }

        public T[] ToArray<T>()
        {
            T[] r = new T[this.Count];
            int i = 0;
            Reset();
            foreach (T item in this)
            {
                r[i] = item;
                i++;
            }
            return r;
        }
        #endregion

        /// <summary>
        /// Наименование тэга - под которым будут серилизоваться элементы списка - если не заданно 
        /// </summary>
        public virtual string ListCaption
        {
            get { return this.GetType().Name; }

        }

        /// <summary>
        /// Если указан тип - элементы списка будут инстанцироваться c заданной сборкой. Сборка в XML - игноируется.
        /// </summary>
        public virtual string ItemAssemblyName
        {
            get { return string.Empty; }
        }

        /// <summary>
        /// Если указан тип - элементы списка будут инстанцироваться заданным типом. Тип указанный в XML - игноируется.
        /// </summary>
        public virtual string ItemAssemblyType
        {
            get { return string.Empty; }
        }
        #endregion


        #region Opertors
        public static EntityList operator +(EntityList L1, EntityList L2)
        {
            HashSet<EntitySingle> hs1 = L1.ToHashSet();
            HashSet<EntitySingle> hs2 = L2.ToHashSet();
            hs1.SymmetricExceptWith(hs2);
            L1.Clear();            
            L1.Add(hs1.ToArray<EntitySingle>());
            
            return L1;
        }
        #endregion

    }    
}
